/*
 * Copyright (c) 2007, TopCoder, Inc. All rights reserved.
 */

using System;
using System.Collections.Generic;
//using TopCoder.Util.ConfigurationManager;
//using TopCoder.Util.ObjectFactory;

namespace TopCoder.Util.Calendar.Entry
{
    /// <summary>
    /// <para>
    /// This is a utility class for this component which is provided so the using application could
    /// use this class to have the flexibility of dynamically plugging in <c>ICalendarEntryDataSource</c>
    /// instance whenever desired without changing existing code.
    /// </para>
    /// <para>
    /// This manager class also provides the sorting functionality, which allows sorting on any field or
    /// a combination of fields as long as a proper <c>IComparer&lt;BaseCalendarEntry&gt;</c> implementation
    /// is provided.
    /// </para>
    /// <para>External applications are strongly recommended to use this class instead of the concrete
    /// <c>ICalendarEntryDataSource</c> implementations to gain the extra flexibility.
    /// </para>
    /// </summary>
    /// <remarks>
    /// Thread Safety: This class is thread safe since it's immutable.
    /// </remarks>
    ///
    /// <author>snower, fuyun</author>
    /// <version>1.0</version>
    /// <copyright>Copyright (c) 2007, TopCoder, Inc. All rights reserved.</copyright>
    public class CalendarEntryUtility
    {
        /// <summary>
        /// Property name for object factory namespace.
        /// </summary>
        private const string OBJ_FACTORY_NAMESPACE_PROP = "of_namespace";

        /// <summary>
        /// Property name for the data source key.
        /// </summary>
        private const string DATA_SOURCE_KEY = "data_source_key";

        /// <summary>
        /// <para>Represents the data source instance from which to search calendar entries.</para>
        /// <para>It's initialized in the constructor and will not change afterwards. It could never be null.</para>
        /// </summary>
        private readonly ICalendarEntryDataSource dataSource;

        /// <summary>
        /// <para>Creates a new <c>CalendarEntryUtility</c> instance from the default namespace.</para>
        /// <para>The default namespace is the full name of this class.</para>
        /// <para>
        /// There are two properties:of_namespace and dataSource. The of_namespace defines the namespace of
        /// object factory. If it is not defined, the default namespace will be used to create object factory.
        /// The data_source_key is a required property which defined the name defined in object factory's
        /// namespace to specify the object to create by object factory.
        ///
        /// The following is one configuration sample:
        ///
        /// <example>
        /// &lt;ConfigManager&gt;
        ///   &lt;namespace name="TopCoder.Util.Calendar.Entry.CalendarEntryUtility"&gt;
        ///     &lt;property name="of_namespace"&gt;
        ///         &lt;value&gt;TopCoder.Util.Calendar.Entry.ObjectFactory&lt;/value&gt;
        ///     &lt;/property&gt;
        ///     &lt;property name="data_source_key"&gt;
        ///          &lt;value&gt;dataSource&lt;/value&gt;
        ///     &lt;/property&gt;
        ///   &lt;/namespace&gt;
        /// &lt;/ConfigManager&gt;
        /// </example>
        /// </para>
        /// </summary>
        /// <exception cref="ConfigurationException">
        /// if any error occurs when loading configuration data or any configuration data is invalid.
        /// </exception>
//        public CalendarEntryUtility()
//            : this(typeof (CalendarEntryUtility).FullName)
//        {
//        }

        /// <summary>
        /// <para>Creates a new <c>CalendarEntryUtility</c> instance from the given namespace.</para>
        /// <para>
        /// There are two propertys:of_namespace and dataSource. The of_namespace defines the namespace of
        /// object factory. If it is not defined, the default namespace will be used to create object factory.
        /// The data_source_key is a required property which defined the name defined in object factory's
        /// namespace to specify the object to create by object factory.
        ///
        /// The following is one configuartion sample:
        ///
        /// <example>
        /// &lt;ConfigManager&gt;
        ///   &lt;namespace name="TopCoder.Util.Calendar.Entry.CalendarEntryUtility"&gt;
        ///     &lt;property name="of_namespace"&gt;
        ///         &lt;value&gt;TopCoder.Util.Calendar.Entry.ObjectFactory&lt;/value&gt;
        ///     &lt;/property&gt;
        ///     &lt;property name="data_source_key"&gt;
        ///          &lt;value&gt;dataSource&lt;/value&gt;
        ///     &lt;/property&gt;
        ///   &lt;/namespace&gt;
        /// &lt;/ConfigManager&gt;
        /// </example>
        /// </para>
        /// </summary>
        /// <param name="namespace">the namespace to create this CalendarEntryUtility instance from.</param>
        /// <exception cref="ArgumentNullException">
        /// if namespace argument is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// if namesapce argument is an empty string.
        /// </exception>
        /// <exception cref="ConfigurationException">
        /// if any error occurs when loading configuration data or any configuration data is invalid.
        /// </exception>
//        public CalendarEntryUtility(string @namespace)
//        {
//            if (@namespace == null)
//            {
//                throw new ArgumentNullException("@namespace", "The argument @namespace can not be null");
//            }
//            if (@namespace.Trim().Length == 0)
//            {
//                throw new ArgumentException("The argument @namespace must be non-empty string", "@namespace");
//            }
//            if (!NamespaceExists(@namespace))
//            {
//                throw new ConfigurationException("The namespace " + @namespace + " does not exist.");
//            }
//
            // get the object factory namespace, it is optional.
//            string of_namespace = GetPropertyValue(@namespace, OBJ_FACTORY_NAMESPACE_PROP, false);
            // get the data source key, it is required.
//            string dataSourceKey = GetPropertyValue(@namespace, DATA_SOURCE_KEY, true);
//
//            ObjectFactory.ObjectFactory of;
//            try
//            {
//                if (of_namespace != null)
//                {
                    // the object factory namespace is defined.
//                    of = ObjectFactory.ObjectFactory.GetDefaultObjectFactory(of_namespace);
//                }
//                else
//                {
                    // use the default namespace for the object factory.
//                    of = ObjectFactory.ObjectFactory.GetDefaultObjectFactory();
//                }
//            }
//            catch (ObjectSourceException ose)
//            {
//                throw new ConfigurationException(
//                    "Fail to create object factory due to the configuration specific exception or missing " +
//                    "property or parse error.", ose);
//            }
//            catch (ObjectCreationException oce)
//            {
//                throw new ConfigurationException("Fails to create object factory.", oce);
//            }
//
//
//            try
//            {
//                dataSource = (ICalendarEntryDataSource) of.CreateDefinedObject(dataSourceKey);
//            }
//            catch (ObjectSourceException ose)
//            {
//                throw new ConfigurationException(
//                    "Fail to create data source object due to implementation specific exception, Configuration " +
//                    "Manager exception for a configuration file or implementation specific problem.",
//                    ose);
//            }
//            catch (ObjectCreationException oce)
//            {
//                throw new ConfigurationException(
//                    "Fail to create data source object due to reflection exception or object creation problem.",
//                    oce);
//            }
//            catch (InvalidCastException ice)
//            {
                // the exception could be thrown if the object defined is not type of ICalendarEntryDataSource
//                throw new ConfigurationException(
//                    "The defined data source object is not the implementation of interface ICalendarEntryDataSource.",
//                    ice);
//            }
//
//            if (dataSource == null)
//            {
//                throw new ConfigurationException("The defined data source could not be created (It is null).");
//            }
//        }

        /// <summary>
        /// Creates a new <c>CalendarEntryUtility</c> instance using the given data source.
        /// </summary>
        /// <param name="dataSource">the dataSource used to create this <c>CalendarEntryUtility</c> instance.</param>
        /// <exception cref="ArgumentNullException">if the given dataSource is null.</exception>
        public CalendarEntryUtility(ICalendarEntryDataSource dataSource)
        {
            if (dataSource == null)
            {
                throw new ArgumentNullException("dataSource", "The argument dataSource can not be null");
            }
            this.dataSource = dataSource;
        }


        /// <summary>
        /// Finds all calendar entries from the data source layer. If no entry exists, an empty list
        /// will be returned.
        /// </summary>
        /// <returns>all calendar entries stored in the data source or an empty list if no entry exists.</returns>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public IList<BaseCalendarEntry> FindAllEntries()
        {
            return dataSource.FindAllEntries();
        }

        /// <summary>
        /// Finds the calendar entry with the given id, if no such entry exists, null will be returned.
        /// </summary>
        /// <param name="id">the id of the desired calendar entry.</param>
        /// <returns>the calendar entry with the given id or null if no such entry exists.</returns>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public BaseCalendarEntry FindEntryById(int id)
        {
            return dataSource.FindEntryById(id);
        }

        /// <summary>
        /// Finds all calendar entries with the given type, if no such records exists, an empty list will be returned.
        /// </summary>
        /// <param name="type">the type by which to search calendar entries.</param>
        /// <returns>all calendar entries with the given type or an empty list if no such entry exists.</returns>
        /// <exception cref="ArgumentNullException">
        /// if type argument is null.
        /// </exception>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public IList<BaseCalendarEntry> FindEntriesByType(CalendarEntryType type)
        {
            return dataSource.FindEntriesByType(type);
        }

        /// <summary>
        /// <para>
        /// Finds all calendar entries whose description partially matches the given description. If no such entry
        /// exists, an empty list will be returned.
        /// </para>
        /// <para>
        /// Partially match the given description is a substring of the description of any desired entry.
        /// </para>
        /// </summary>
        /// <param name="description">the description by which to search calendar entries.</param>
        /// <returns>
        /// all calendar entries whose description partially matches the given description or an empty list
        /// if no such entry exists.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// if the description argument is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// if description argument is an empty string.
        /// </exception>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public IList<BaseCalendarEntry> FindEntriesByDescription(string description)
        {
            return dataSource.FindEntriesByDescription(description);
        }

        /// <summary>
        /// Finds all calendar entries within the given date range(inclusive), if no such entry exists,
        /// an empty list will be returned.
        /// </summary>
        /// <param name="startDate">the start date of the date range by which to search calendar entries.</param>
        /// <param name="endDate">the end date of the date range by which to search calendar entries.</param>
        /// <returns>
        /// all calendar entries within the given date range or an empty list if no such entry exists.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// if endDate is before startDate.
        /// </exception>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public IList<BaseCalendarEntry> FindEntriesByDateRange(DateTime startDate, DateTime endDate)
        {
            return dataSource.FindEntriesByDateRange(startDate, endDate);
        }

        /// <summary>
        /// Finds the calendar entry type with the given id, if no such entry type exists, null will be returned.
        /// </summary>
        /// <param name="typeId">the id of the desired calendar entry type.</param>
        /// <returns>the desired calendar entry type or null if no such type exists.</returns>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public CalendarEntryType FindEntryTypeById(int typeId)
        {
            return dataSource.FindEntryTypeById(typeId);
        }

        /// <summary>
        /// <para>
        /// Finds all calendar entry types whose description partially matches the given description.
        /// If no such entry type exists, an empty list will be returned.
        /// </para>
        /// <para>Partially match the given description is a substring of the description of any desired entry type.
        /// </para>
        /// </summary>
        /// <param name="description">the description used to search calendar entry types.</param>
        /// <returns>
        /// all calendar entry types whose description partially matches the given description or an
        /// empty list if no such entry type exists.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// if the description argument is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// if description argument is an empty string.
        /// </exception>
        /// <exception cref="CalendarEntryDataSourceException">
        /// if any errors occur in the data source layer.
        /// </exception>
        public IList<CalendarEntryType> FindEntryTypesByDescription(string description)
        {
            return dataSource.FindEntryTypesByDescription(description);
        }

        /// <summary>
        /// Sorts the given entries by date field in ascending order.
        /// </summary>
        /// <param name="entries">a collection of calendar entries to be sorted.</param>
        /// <returns>a sorted list of calendar entries by the date field in ascending order.</returns>
        /// <exception cref="ArgumentNullException">
        /// if the parameter is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// if the entries collection contains null element or if the implementation of comparer caused
        /// an error during the sort.
        /// </exception>
        public static List<BaseCalendarEntry> SortEntries(ICollection<BaseCalendarEntry> entries)
        {
            return SortEntries(entries, new CalendarEntryDateComparer());
        }

        /// <summary>
        /// Sorts the given entries using the given comparer.
        /// </summary>
        /// <param name="entries">the calendar entries to be sorted.</param>
        /// <param name="comparer">the comparer used to sort calendar entries.</param>
        /// <returns>a sorted list of calendar entries.</returns>
        /// <exception cref="ArgumentNullException">
        /// if any parameter is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// if the entries collection contains null element or if the implementation of comparer caused
        /// an error during the sort.
        /// </exception>
        public static List<BaseCalendarEntry> SortEntries(ICollection<BaseCalendarEntry> entries,
                                                          IComparer<BaseCalendarEntry> comparer)
        {
            if (entries == null)
            {
                throw new ArgumentNullException("entires", "The parameter entries could not be null.");
            }

            if (comparer == null)
            {
                throw new ArgumentNullException("comparer", "The parameter comparer could not be null.");
            }

            List<BaseCalendarEntry> newEntries = new List<BaseCalendarEntry>();

            // copy the entries to a new list and check the null element as well.
            foreach (BaseCalendarEntry entry in entries)
            {
                if (entry == null)
                {
                    throw new ArgumentException("The parameter entries contains null element.", "entries");
                }
                newEntries.Add(entry);
            }
            try
            {
                // sort using the comparer.
                newEntries.Sort(comparer);
            }
            catch (InvalidOperationException ioe)
            {
                // InvalidOperationException will be thrown if the comparer throws the ArgumentException,
                // for example, the Date of the entry is DateTime.MinDate.
                throw new ArgumentException("The underlying comparer throws an exception.", ioe);
            }
            return newEntries;
        }

        /// <summary>
        /// Get configuration parameter from given namespace and property.
        /// </summary>
        /// <param name="ns">namespace to get parameter</param>
        /// <param name="prop">property name of the parameter to get</param>
        /// <param name="required">if the property is required</param>
        /// <returns>
        /// the parameter value of the given namespace and property or null if the property
        /// is optional and not in the namespace.
        /// </returns>
        /// <exception cref="ConfigurationException">
        /// the property is not in the namespace but it is required or the property value is
        /// empty or any other error occurs.
        /// </exception>
//        private static string GetPropertyValue(string ns, string prop, bool required)
//        {
//            string value;
//            try
//            {
                // try to get the value.
//                value = ConfigManager.GetInstance().GetValue(ns, prop);
//            }
//            catch (Exception e)
//            {
//                throw new ConfigurationException("Fail to get configuration parameter.", e);
//            }
//
            // it is required and not defined.
//            if (value == null && required)
//            {
//                throw new ConfigurationException("Required property '" + prop +
//                                                 "' in namespace '" + ns + "' is missing.");
//            }
//
            // optional parameter can be missing, but mustn't be empty string
//            if (value != null && value.Trim().Length == 0)
//            {
//                throw new ConfigurationException("Property '" + prop +
//                                                 "' in namespace '" + ns + "' is empty string.");
//            }
//            return value;
//        }

        /// <summary>
        /// Tests if the given namespace exists.
        /// </summary>
        /// <param name="ns">the namespace to check</param>
        /// <returns>true if the given namespace exists, false otherwise</returns>
//        private static bool NamespaceExists(string ns)
//        {
//            try
//            {
//                return ConfigManager.GetInstance().GetNamespace(ns) != null;
//            }
//            catch
//            {
//                return false;
//            }
//        }
    }
}